#include "../liolib/lioflat.h"
#define VERTEX glVertex3fv

GLfloat
	LightAmbient[]= { 0.1f, 0.1f, 0.1f, 1.0f }, // Ambient Light Values
	LightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f }, // Diffuse Light Values
	LightPosition[]= { 0.0f, 0.0f, 0.0f, 1.0f }, // Light Position
	LightSpecular[]= { 0.1f, 0.1f, 0.1f, 1.0f }, // Light Specular
	LightAttenuate[]= { 1.0f, 0.005f, 0.001f }, // Light Attenuation
	fogColor[4]= {0.0f, 0.0f, 0.0f, 1.0f}; // Fog Color

typedef float Point[3];

static CSTR change_names[] = {"angle", "R1", "R2", "floors", "precision"};

class ArchiCube:public FlatApplication
{
protected:
	Vector<double>loopTimes;
	float a,b,wz,d1,d2,slope1,slope2,slope,Hm,dh,camAngleZ,R1m,R2m,base;
	LONG wx,wy;
	double win1[3],win2[3],obj[3];
	int res,color,count,angle_deg,floors,change;
	Transform trans;
	DisplayList list;
	Point *r1,*r2,*n1,*n2;
	Point2d<int> oldMousePos;
	Vector<Point> br1,br2,bn1,bn2;
	float distX,distY,distZ;
	float angleX,angleY,angleZ;
	BOOL loop;

	BOOL create();
	BOOL glCreate();
	void glDestroy();
	void destroy();
	void reshape ();
	BOOL listen(WORD id, WORD code, const void *info);
	void draw2D();
	void draw3D();
	BOOL input(BYTE type);
	void updateSelection();
	void updateList();
	void update();
	void recalc();
	void capture();
};

static float getSlope(float a, float h, float r){return atan2f(h,a*r);}

void ArchiCube::capture()
{
	Image screen;
	screenshot(&screen);
	writeJPEG(_T("screenshot.jpg"),&screen);
}
BOOL ArchiCube::create ()				
{
	if(!FlatApplication::create())
		return FALSE;
	name=_T("ArchiCube");
	display.width=1024;
	display.height=768;
	display.bpp=32;
	res=0;
	a=5.0f;
	b=0.5f;
	clearColor=INT_WHITE;
#ifdef _DEBUG
	loopTimes.setSize(100,MEM_OP_CLEAR);
	console.open();
#endif
	InitInfo info;
	info.read("setup.ini");
	count = info.getInt("precision",45);
	angle_deg = info.getInt("rotation",270);
	base = (float)info.getDouble("side",9);
	floors = info.getInt("floors",3);
	R1m = (float)info.getDouble("R1",2);
	R2m = (float)info.getDouble("R2",4);
	color=info.getInt("color", INT_WYELLOW, _T("%X"));
	change=info.getInt("change", 0);
	loop=info.getInt("loop", 1);

	distZ=(float)info.getDouble("distZ",-30);
	distX=(float)info.getDouble("distX",0);
	distY=(float)info.getDouble("distY",0);
	angleX=(float)info.getDouble("angleX",0);
	angleZ=(float)info.getDouble("angleZ",-10);
	angleY=(float)info.getDouble("angleY",0);

	recalc();
	return TRUE;
}

void ArchiCube::destroy()
{
	InitInfo info;
	
	info.addInt("precision",count);
	info.addInt("rotation",angle_deg);
	info.addDouble("side",base);
	info.addInt("floors",floors);
	info.addDouble("R1",R1m);
	info.addDouble("R2",R2m);
	info.addInt("color", color, _T("%X"));
	info.addInt("change", change);
	info.addInt("loop", loop);

	info.addDouble("distZ",distZ);
	info.addDouble("distX",distX);
	info.addDouble("distY",distY);
	info.addDouble("angleX",angleX);
	info.addDouble("angleZ",angleZ);
	info.addDouble("angleY",angleY);

	info.write("setup.ini");

	FlatApplication::destroy();
}

void ArchiCube::recalc()
{
	Hm = float(base) / floors;
	float tmp = 2 * a / base;
	dh = tmp * Hm;
	float R1 = tmp * R1m;
	float R2 = tmp * R2m;
	float angle = float(DEG_TO_RAD*angle_deg);

	slope1 = getSlope(angle,Hm,R1m);
	slope2 = getSlope(angle,Hm,R2m);
	slope = getSlope(angle,Hm,0.5f*(R1m+R2m));

	float da = angle/count;
	float ddh = dh/count;
	r1=br1.setSize(count+1);
	r2=br2.setSize(count+1);
	n1=bn1.setSize(count+1);
	n2=bn2.setSize(count+1);
	float th=0,ta=0,s,c;

	initVector(r1[0],R1,0,0);
	initVector(r2[0],R2,0,0);
	for(int i=0;i<count;i++)
	{
		ta+=da;
		th+=ddh;
		s=sinf(ta);
		c=cosf(ta);
		
		initVector(r1[i+1],c*R1,s*R1,th);
		initVector(r2[i+1],c*R2,s*R2,th);

		getNormalVector(r2[i],r1[i],r1[i+1],n1[i]);
		getNormalVector(r2[i+1],r2[i],r1[i],n2[i]);
	}

	getNormalVector(r1[count-1],r1[count],r2[count],n1[count]);
	getNormalVector(r1[count],r2[count],r2[count-1],n2[count]);
}

BOOL ArchiCube::glCreate ()
{
	if(!FlatApplication::glCreate())
		return FALSE;
	setSmooth(TRUE,GL_NICEST);
	setTexturing(FALSE);
	createLight(GL_LIGHT1,LightAmbient,LightDiffuse,LightPosition,LightSpecular,LightAttenuate);
	setDepthTest(TRUE,1,GL_LESS);
	setPolygonOffset(TRUE);

	updateList();

	return TRUE;
}

void ArchiCube::updateList()
{
	CompileDisplayList compile(&list);

	setLight();

	setColor(INT_BLACK);
	fillPolygon(FALSE);
	drawParallel(a,a,a);

	fillPolygon(TRUE);
	glPushMatrix();
	glTranslated(a,a,a);
	setColor(res==1?INT_WRED:INT_DRED);
	drawSphere(b);
	glPopMatrix();

	glPushMatrix();
	glTranslated(-a,-a,-a);
	setColor(res==2?INT_WBLUE:INT_DBLUE);
	drawSphere(b);
	glPopMatrix();

	/*
	setColor(color);
	glBegin(GL_QUADS);
		// Top Face
		glNormal3f(0, 0, 1.0f);
		glVertex3f(-a, -a, 0);
		glVertex3f(-a, a, 0);
		glVertex3f(a, a, 0);
		glVertex3f(a, -a, 0);
	glEnd();
	*/

	glPushMatrix();
	glTranslated(0,0,-a);
	for(int j=0;j<floors;j++)
	{
		Point v;
		setColor(color);
		fillPolygon(TRUE);
		glBegin(GL_QUAD_STRIP);
			for(int i=0;i<=count;i++)
			{
				glNormal3fv(n1[i]);
				VERTEX(r1[i]);
				glNormal3fv(n2[i]);
				VERTEX(r2[i]);
				
			}
		glEnd();

		fillPolygon(FALSE);
		setColor(INT_BLACK);
		glBegin(GL_LINE_STRIP);
			for(int i=0;i<=count;i++)
				VERTEX(r1[i]);
		glEnd();
		glBegin(GL_LINE_STRIP);
			for(int i=0;i<=count;i++)
				VERTEX(r2[i]);
		glEnd();
		glBegin(GL_QUAD_STRIP);
			for(int i=0;i<=count;i++)
			{
				addVector(r1[i], n1[i], v);
				VERTEX(r1[i]);
				VERTEX(v);
			}
		glEnd();
		glBegin(GL_QUAD_STRIP);
			for(int i=0;i<=count;i++)
			{
				addVector(r2[i], n2[i], v);
				VERTEX(r2[i]);
				VERTEX(v);
			}
		glEnd();
		glTranslated(0,0,dh);
	}
	glPopMatrix();
}

void ArchiCube::glDestroy()
{
	FlatApplication::glDestroy();
}

void ArchiCube::reshape()
{
	FlatApplication::reshape();
}

BOOL ArchiCube::listen(WORD id, WORD code, const void *info)
{
	return FALSE;
}

void ArchiCube::draw2D()
{
	font.color=INT_BLACK;
	font.x=10;
	font.y=20;
	glPrint(&font,_T("SETUP: A = %d deg, R1 = %.1f m, R2 = %.1f m, H = %.2f m, P = %d"),angle_deg,R1m,R2m,Hm,count);
	font.y+=15;
	glPrint(&font,_T("SLOPE1: %.1f deg, SLOPE2: %.1f deg, SLOPE: %.1f deg"),RAD_TO_DEG*slope1,RAD_TO_DEG*slope2,RAD_TO_DEG*slope);
	font.y=float(window.height-30);
	glPrint(&font,_T("CHANGING: [%d] %s"),change+1,change_names[change]);
	font.y+=15;
	glPrint(&font,_T("CONTROL: +,-,numbers,arrows,drag,scroll"));
	/*
	glPrint(&font,_T("  fps: %10.3f"),INV(loopTime));
	font.y+=15;
	glPrint(&font,_T("mouse: %10d, %10d, %10.6f"),wx,wy,wz);
	font.y+=15;
	glPrint(&font,_T("  obj: %10.3f, %10.3f, %10.6f"),obj[0],obj[1],obj[2]);
	font.y+=15;
	glPrint(&font,_T(" obj1: %10.3f, %10.3f, %10.6f, %10.6f [ %s ]"),win1[0],win1[1],win1[2], d1, res==1?"*":" ");
	font.y+=15;
	glPrint(&font,_T(" obj2: %10.3f, %10.3f, %10.6f, %10.6f [ %s ]"),win2[0],win2[1],win2[2], d2, res==2?"*":" ");
	*/
	font.x=float(window.width - 200);
	font.y=20.0f;
	int count=loopTimes.size;
	if(count)
	{
		float avg_fps,min_fps,max_fps,avgl,minl,maxl,value;
		double *values=loopTimes.elems;
		loopTimes.shift();
		avgl=minl=maxl=float(values[count-1]=loopTime);
		for(int i=0;i<count-1;i++)
		{
			value=(float)loopTimes.elems[i];
			if(value)
			{
				avgl+=value;
				if(minl>value)minl=value;
				else if(maxl<value)maxl=value;
			}
		}
		avg_fps=avgl?count/avgl:0;
		min_fps=maxl?1/maxl:0;
		max_fps=minl?1/minl:0;
		glPrint(&font,_T("FPS: %4.0f/%4.0f/%4.0f"), min_fps,avg_fps,max_fps);
	}
}

void ArchiCube::draw3D()
{
	glTranslatef(distX,distY,distZ);
	glRotate(90.0f,0,0,FALSE);
	glRotate(angleX,angleY,angleZ);
	
	glPushMatrix();
	fillPolygon(TRUE);
	glRotate(0,0,camAngleZ);
	glTranslatef(1.1f*a, 1.3f*a, 1.6f*a);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);			// Position The Light
	setColor(INT_YELLOW);
	drawSphere(b);
	glPopMatrix();

	trans.update();
	list.execute();
}

void ArchiCube::update()
{
	if(loop)
	{
		camAngleZ+=20*float(loopTime);
		updated=TRUE;
	}
}

void ArchiCube::updateSelection()
{
	trans.getWindowCoor(a,a,a,win1);
	trans.getWindowCoor(-a,-a,-a,win2);
	wx=mouse.x;
	wy=window.height-mouse.y;
	trans.getWindowDepth(wx,wy,&wz);
	trans.getObjectCoor(wx,wy,wz,obj);

	d1=sqrtf(float(pow2(a-obj[0])+pow2(a-obj[1])+pow2(a-obj[2])));
	d2=sqrtf(float(pow2(a+obj[0])+pow2(a+obj[1])+pow2(a+obj[2])));
	int res1=d1<=d2?(d1<=1.1*b?1:0):(d2<=1.1*b?2:0);
	if(res1!=res)
	{
		res=res1;
		updateList();
	}
}

BOOL ArchiCube::input(BYTE type)
{
	if(FlatApplication::input(type))
		return TRUE;
	switch(type)
	{
		case MOUSE_INPUT_MSG:
		{
			switch(msg.message)
			{
				case WM_LBUTTONDOWN:
					updateSelection();
					break;
				case WM_MOUSEMOVE:
				{
					if(mouse.lbutton)
					{
						angleZ+=(360.0f*(mouse.x-oldMousePos.x))/window.width;
						angleX+=(360.0f*(mouse.y-oldMousePos.y))/window.height;
					}
					oldMousePos.set(mouse.x,mouse.y);
				}
				break;
				case WM_MOUSEWHEEL:
				{
					distZ+=0.01f*(screen.zFarPers-screen.zNearPers)*mouse.wheel;
					if(distZ<-screen.zFarPers)
						distZ=float(-screen.zFarPers);
					else if(distZ>-screen.zNearPers)
						distZ=float(-screen.zNearPers);
				}
				default:
					return FALSE;
			}
			return TRUE;
		}
		break;
		case KEY_INPUT_MSG:
		{
			if(keyboard.pressed)
			{
				if(keyboard.waiting)switch(keyboard.code)
				{
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
						change=keyboard.code-49;
						break;
					case VK_ESCAPE:
						terminate();
						break;
					case VK_SPACE:
						loop=!loop;
						break;
					case VK_LEFT:
						angleZ-=1.0f;
						break;
					case VK_RIGHT:
						angleZ+=1.0f;
						break;
					case VK_DOWN:
						angleX+=1.0f;
						break;
					case VK_UP:
						angleX-=1.0f;
						break;
					case VK_F1:
						toggleFullscreen();
						break;
					case VK_F5:
						capture();
						break;
					default:
						return FALSE;
				}
				else switch(keyboard.symbol)
				{
					case '=':
					case '+':
						switch(change)
						{
						case 0:
							if(angle_deg<360)
								angle_deg++;
							break;
						case 1:
							if(R1m<R2m)
								R1m+=0.1f;
							break;
						case 2:
							if(R2m<base)
								R2m+=0.1f;
							break;
						case 3:
							if(Hm>2)
								floors++;
							break;
						case 4:
							if(count<angle_deg)
								count++;
							break;
						}
						recalc();
						updateList();
						break;
					case '-':
					case '_':
						switch(change)
						{
						case 0:
							if(angle_deg>0)
								angle_deg--;
							break;
						case 1:
							if(R1m>0)
								R1m-=0.1f;
							break;
						case 2:
							if(R2m>R1m)
								R2m-=0.1f;
							break;
						case 3:
							if(floors>1)
								floors--;
							break;
						case 4:
							if(count>1)
								count--;
							break;
						}
						recalc();
						updateList();
						break;
					default:
						return FALSE;
				}
				return TRUE;
			}
		}
	}
	return FALSE;
}


int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
	Debugger output(_T("debug.txt"));
	BOOL exc=TRUE;
	int result=1,exc_code=0,throw_line=0;
	CSTR exc_msg=NULL,throw_source=NULL;
	try
	{
		ArchiCube app;
		PRINT(_T("\n\n--------------------- Application started.\n"));PRINTMEM();
		result=app.main(hInstance,hPrevInstance,lpCmdLine,nCmdShow);
		exc=FALSE;
	}
	catch(const char * str){exc_msg=str;}
	catch(int n){exc_code=n;}
	catch(...){}
	if(exc)
	{
		PRINT(	_T("\n\n--------------------- Application intexcupted by an exception!\nError code: %d\nError msg: %s\n"),
				exc_code, exc_msg?exc_msg:"none");
	}
	PRINT(_T("\n\n--------------------- Application closed.\n"));PRINTMEM(TRUE);
	return result;
}